package com.atguigu.gmall.item.service.impl;

import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.JSONs;
import com.atguigu.gmall.feign.product.SkuDetailItemFeignClient;
import com.atguigu.gmall.item.service.SkuDetailService;
import com.atguigu.gmall.item.to.CategoryView;
import com.atguigu.gmall.item.to.SkuDetailTO;
import com.atguigu.gmall.product.entity.SkuImage;
import com.atguigu.gmall.product.entity.SkuInfo;
import com.atguigu.gmall.product.entity.SpuSaleAttr;
import com.atguigu.starter.cache.annotation.GmallCache;
import com.atguigu.starter.cache.service.Bitmapservice;
import com.atguigu.starter.cache.service.CacheService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @Author:lyd
 * @Date:2023/8/18 20:33
 * @Version:1.0
 * @Description
 **/
@Service
@Slf4j
public class SkuDetailServiceImpl implements SkuDetailService {

    @Autowired
    private SkuDetailItemFeignClient skuDetailItemFeignClient;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private CacheService cacheService;

    @Autowired
    private Bitmapservice bitmapservice;

    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    ThreadPoolExecutor executor;

    /**
     * 使用AOP的方式，将防止缓存穿透、击穿、雪崩等非核心业务代码抽取出来，实现更加通用的业务代码
     * @param skuId
     * @return
     */
    @GmallCache(key = RedisConst.SKU_INFO_CACHE_KEY+"#{#params[0]}",
                bitmap = RedisConst.BITMAP_SKUIDS,
                bitmapOffset="#{#params[0]}",
                dataTTL = RedisConst.SKU_INFO_TTL)
    @Override
    public SkuDetailTO getSkuDetail(Long skuId) {
        log.info("回源查询数据库中......");
        return getSkuDetailByCompletableFuture(skuId);
    }

    /**
     * 使用redisson的方式，在高并发下从缓存中获取数据
     * @param skuId
     * @return
     */
    private SkuDetailTO getSkuDetailTOWithRedisson(Long skuId) {
        String cacheKey = RedisConst.SKU_INFO_CACHE_KEY + skuId;
        String bitmap = RedisConst.BITMAP_SKUIDS;
        SkuDetailTO cacheData = cacheService.get(cacheKey, SkuDetailTO.class);
        if (!StringUtils.isEmpty(cacheData)) {
            log.info("缓存中有数据");
            return cacheData;
        }
        log.info("缓存中没有数据,查询位图");
        Boolean result = bitmapservice.constain(bitmap, skuId);
        //位图中没有对应skuId
        if (!result) {
            log.warn("这可能是一个攻击请求，已被拦截，访问商品的id：[{}]", skuId);
            throw new GmallException(ResultCodeEnum.SKU_INFO_ID_NOT_EXIST);
        }
        //位图中有对应id,进行回源操作
        RLock lock = null;
        try {
            lock = redissonClient.getLock(RedisConst.LOCK_PREFIX + skuId);
            boolean tryLock = lock.tryLock();
            //抢锁成功
            if (tryLock) {
               log.info("执行回源操作");
                SkuDetailTO skuDetailTO = getSkuDetailByResource(skuId);
                //存入缓存
                String jsonStr = JSONs.toJSONStr(skuDetailTO);
                cacheService.save(cacheKey, jsonStr, RedisConst.SKU_INFO_TTL);
                return skuDetailTO;
            }
        } finally {
            try {
                lock.unlock();
            } catch (Exception e) {
                log.warn("完成回源操作，差点解了别人的锁");
            }
        }

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.info("缓存未命中，抢锁失败，等待两秒再从缓存中查询数据");
        //再次从缓存中获取数据
        return cacheService.get(cacheKey, SkuDetailTO.class);
    }

    /**
     * 简单方式从缓存中获取数据,高并发模式下不可这样使用
     *
     * @param skuId
     * @return
     */
    public SkuDetailTO getSkuDetailSample(Long skuId) {
        String cacheData = stringRedisTemplate.opsForValue().get(RedisConst.SKU_INFO_CACHE_KEY + skuId);
        if (!StringUtils.isEmpty(cacheData)) {
            SkuDetailTO detail = JSONs.jsonToObj(cacheData, SkuDetailTO.class);
            return detail;
        }
        SkuDetailTO skuDetailTO = getSkuDetailByResource(skuId);
        //3、商品价格
        BigDecimal price = skuDetailItemFeignClient.getPrice(skuId).getData();
        skuDetailTO.setPrice(price);
        stringRedisTemplate.opsForValue().set(RedisConst.SKU_INFO_CACHE_KEY + skuId, JSONs.toJSONStr(skuDetailTO));
        return skuDetailTO;
    }


    /**
     * 使用异步编排的方式从数据库中获取数据,【推荐】
     * @param skuId
     * @return
     */
    private SkuDetailTO getSkuDetailByCompletableFuture(Long skuId) {
        log.info("商品详情：【{}】； 缓存未命中；正在回源", skuId);
        SkuDetailTO detail = new SkuDetailTO();
        SkuDetailTO.SkuInfoVo skuInfoVo = new SkuDetailTO.SkuInfoVo();
        //1、使用异步编排的方式获取商品基本信息
        CompletableFuture<SkuInfo> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = skuDetailItemFeignClient.getSkuinfo(skuId).getData();
            skuInfoVo.setId(skuInfo.getId());
            skuInfoVo.setSkuName(skuInfo.getSkuName());
            skuInfoVo.setSpuId(skuInfo.getSpuId());
            skuInfoVo.setSkuDefaultImg(skuInfo.getSkuDefaultImg());
            skuInfoVo.setWeight(skuInfo.getWeight());
            detail.setSkuInfo(skuInfoVo);
            return skuInfo;
        },executor);


        //2、使用异步编排的方式获取商品图片
        CompletableFuture<Void> imagesFuture = CompletableFuture.runAsync(() -> {
            Result<List<SkuImage>> images = skuDetailItemFeignClient.getImages(skuId);
            List<SkuDetailTO.SkuImageVo> skuImageVos = images.getData().stream()
                    .map(item -> {
                        SkuDetailTO.SkuImageVo skuImageVo = new SkuDetailTO.SkuImageVo();
                        skuImageVo.setImgUrl(item.getImgUrl());
                        return skuImageVo;
                    }).collect(Collectors.toList());
            skuInfoVo.setSkuImageList(skuImageVos);
            detail.setSkuInfo(skuInfoVo);
        }, executor);


        //3、使用异步编排的方式查询分类层级路径
        CompletableFuture<Void> categoryViewFuture = skuInfoFuture.thenAcceptAsync((skuinfo) -> {
            CategoryView categoryView = skuDetailItemFeignClient.getCategoryView(skuinfo.getCategory3Id()).getData();
            detail.setCategoryView(categoryView);
        },executor);
        //4、使用异步编排查询价格
        CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(() -> {
            BigDecimal price = skuDetailItemFeignClient.getPrice(skuId).getData();
            detail.setPrice(price);
        }, executor);

        //4、使用异步编排获取销售属性
        CompletableFuture<Void> spuSaleAttrsFuture = skuInfoFuture.thenAcceptAsync((skuinfo) -> {
            List<SpuSaleAttr> spuSaleAttrs = skuDetailItemFeignClient.getSpuSaleAttr(skuinfo.getSpuId(), skuId).getData();
            detail.setSpuSaleAttrList(spuSaleAttrs);
        },executor);


        //5、使用异步编排获取销valuesSkuJson
        CompletableFuture<Void> valuesSkuJsonFuture = skuInfoFuture.thenAcceptAsync((skuinfo) -> {
            String data = skuDetailItemFeignClient.getValueSkuJson(skuinfo.getSpuId()).getData();
            detail.setValuesSkuJson(data);
        },executor);
        CompletableFuture.allOf(imagesFuture, priceFuture, categoryViewFuture, spuSaleAttrsFuture, valuesSkuJsonFuture)
                .join();
        return detail;
    }

    /**
     * 获取源数据，从数据库中查询数据
     *
     * @param skuId
     * @return
     */
    private SkuDetailTO getSkuDetailByResource(Long skuId) {
        //2、商品基本信息
        SkuDetailTO detail = new SkuDetailTO();
        SkuInfo skuinfo = skuDetailItemFeignClient.getSkuinfo(skuId).getData();
        SkuDetailTO.SkuInfoVo skuInfoVo = new SkuDetailTO.SkuInfoVo();
        BeanUtils.copyProperties(skuinfo, skuInfoVo);

        //4、商品图片
        Result<List<SkuImage>> images = skuDetailItemFeignClient.getImages(skuId);
        List<SkuDetailTO.SkuImageVo> skuImageVos = images.getData().stream()
                .map(item -> {
                    SkuDetailTO.SkuImageVo skuImageVo = new SkuDetailTO.SkuImageVo();
                    skuImageVo.setImgUrl(item.getImgUrl());
                    return skuImageVo;
                }).collect(Collectors.toList());
        skuInfoVo.setSkuImageList(skuImageVos);

        //1、查询分类层级路径
        CategoryView categoryView = skuDetailItemFeignClient.getCategoryView(skuinfo.getCategory3Id()).getData();


        //5、销售属性
        List<SpuSaleAttr> spuSaleAttrs = skuDetailItemFeignClient.getSpuSaleAttr(skuinfo.getSpuId(), skuId).getData();

        //6、valuesSkuJson
        String data = skuDetailItemFeignClient.getValueSkuJson(skuinfo.getSpuId()).getData();

        detail.setSkuInfo(skuInfoVo);
        detail.setCategoryView(categoryView);
        detail.setValuesSkuJson(data);
        detail.setSpuSaleAttrList(spuSaleAttrs);
        return detail;
    }
}
